package de.justphil.tcg.tcgserver.websockets.services;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;

import org.glassfish.grizzly.websockets.DataFrame;
import org.glassfish.grizzly.websockets.WebSocket;
import org.glassfish.grizzly.websockets.WebSocketListener;
import org.slf4j.Logger;

import static de.justphil.tcg.tcgserver.commons.logging.TCGLogging.*;
import de.justphil.tcg.tcgserver.websockets.config.WebSocketConfig;

public class ConnectionMaintainer extends Thread implements WebSocketListener {
	private static final Logger log = logger(ConnectionMaintainer.class);

	private final ConcurrentMap<WebSocket, Long> inGameWebSockets;
	private final ConcurrentMap<WebSocket, Boolean> badConnections;
	private volatile boolean stopped;
	private volatile boolean paused;
	private volatile boolean init;
	private volatile long keepAliveInterval;
	
	
	
	/* ############################################################################################ */
	/* ############################################################################################ */
	/* ############################################################################################ */

	private volatile ConnectionListener connectionListener;
	
	public interface ConnectionListener {
		public void onConnection(WebSocket socket);
		public void onConnectionTimeOut(WebSocket socket);
		public void onConnectionRehab(WebSocket socket);
		public void onConnectionClose(WebSocket socket);
	}
	
	public void setConnectionListener(ConnectionListener connectionListener) {
		this.connectionListener = connectionListener;
	}
	
	public void removeConnectionListener() {
		connectionListener = null;
	}
	
	/* ############################################################################################ */
	/* ############################################################################################ */
	/* ############################################################################################ */

	public ConnectionMaintainer(long keepAliveInterval, TimeUnit keepAliveIntervalUnit) {
		super("ConnectionMaintainerThread");
		inGameWebSockets	= new ConcurrentHashMap<WebSocket, Long>();
		badConnections		= new ConcurrentHashMap<WebSocket, Boolean>();
		stopped				= true;
		paused				= true;
		init				= true;
		this.keepAliveInterval = TimeUnit.MILLISECONDS.convert(keepAliveInterval, keepAliveIntervalUnit);
	}
	
	@Override
	public synchronized void start() {
		stopped = false;
		super.start();
	}

	@Override
	public void run() {
		log.info("START");
		
		while (!stopped) {
			if (Thread.interrupted() || init) { // Thread.interrupted() because the interrupted flag must be cleared
				// pause loop
				synchronized(this) {
					while(paused) {
						try {
							log.info("PAUSE");
							this.wait();
						}
						catch (InterruptedException ie) {
							log.info("[{}] I was interrupted. (1)", Thread.currentThread().getName());
						}
					}
					
					if (stopped)
						break;
					else
						log.info("RESUME");
				}
			}
			
			
			try {
				sleep(keepAliveInterval);
				
				log.info("Starting maintenance routine...");
				maintain();
			}
			catch (InterruptedException ie) {
				log.info("[{}] I was interrupted. (2)", Thread.currentThread().getName());
				this.interrupt(); // in order to set the interrupted flag
			}
		}
		
		log.info("KILLED");
	}
	
	/* ############################################################################################ */
	/* ############################################################################################ */
	/* ############################################################################################ */
	
	public void killMaintainer() {
		if (this.isAlive()) {
			stopped = true;
			paused = false;
			
			if (!this.isInterrupted())
				this.interrupt();
		}
		
		inGameWebSockets.clear();
		badConnections.clear();
	}
	
	public void pauseMaintainer() {
		if (this.isAlive() && !paused) {
			paused = true;
			
			if (!this.isInterrupted())
				this.interrupt();
		}
	}
	
	public boolean isPaused() {
		return (this.isAlive() && paused && this.getState().compareTo(State.WAITING) == 0);
	}
	
	public void resumeMaintainer() {
		if (this.isAlive() && paused) {
			paused = false;
			
			synchronized(this) {
				this.notify();
			}
		}
	}
	
	public Long addInGameWebSocket(WebSocket socket) {
		long initialOffset = TimeUnit.MILLISECONDS.convert(WebSocketConfig.INITIAL_SCAN_OFFSET, WebSocketConfig.INITIAL_SCAN_OFFSET_UNIT);
		Long out = inGameWebSockets.put(socket, System.currentTimeMillis() + initialOffset);
		
		if (paused) {
			resumeMaintainer();
			
			// this will be executed only once (after first 'paused' period)
			if (init)
				init = false;
		}
		
		return out;
	}
	
	public int getInGameWebSocketsCount() {
		return inGameWebSockets.size();
	}
	
	public int getBadConnectionsCount() {
		return badConnections.size();
	}
	
	/* ############################################################################################ */
	/* ############################################################################################ */
	/* ############################################################################################ */
	
	private void alive(WebSocket webSocket) {
		inGameWebSockets.replace(webSocket, System.currentTimeMillis());
		
		if (badConnections.remove(webSocket) != null) {
			if (connectionListener != null)
				connectionListener.onConnectionRehab(webSocket);
		}
	}
	
	private void maintain() {
		long t = 0;
		
		for (WebSocket socket : inGameWebSockets.keySet()) {
			t = System.currentTimeMillis() - inGameWebSockets.get(socket).longValue();
			
			if ( t > keepAliveInterval) {
				log.debug("Connection timeout! (t -> {})", t);
				badConnections.put(socket, true);
				
				if (connectionListener != null) {
					connectionListener.onConnectionTimeOut(socket);
				}
			}
			
			/*
			else {
				log.debug("Connection alive! (t -> {})", t);
			}
			*/
		}
	}
	
	/* ############################################################################################ */
	/* ############################################################################################ */
	/* ############################################################################################ */
	// callback methods
	
	@Override
	public void onPong(WebSocket webSocket, byte[] data) {
		alive(webSocket);
	}
	
	@Override
	public void onMessage(WebSocket webSocket, String data) {
		alive(webSocket);
	}

	@Override
	public void onMessage(WebSocket webSocket, byte[] data) {
		alive(webSocket);
	}
	
	@Override
	public void onPing(WebSocket webSocket, byte[] data) {
		alive(webSocket);
	}
	
	/* ############################################################################################ */
	/* ############################################################################################ */
	/* ############################################################################################ */
	// unused methods

	@Override
	public void onClose(WebSocket webSocket, DataFrame data) {
		inGameWebSockets.remove(webSocket);
		badConnections.remove(webSocket);
		
		if (connectionListener != null)
			connectionListener.onConnectionClose(webSocket);
		
		if (inGameWebSockets.size() == 0) {
			pauseMaintainer();
		}
	}

	@Override
	public void onConnect(WebSocket webSocket) {
		if (connectionListener != null)
			connectionListener.onConnection(webSocket);
	}

	@Override
	public void onFragment(WebSocket arg0, String arg1, boolean arg2) {}

	@Override
	public void onFragment(WebSocket arg0, byte[] arg1, boolean arg2) {}
}
